'use strict';
import { attr, ce, on } from './dom.js';
import { forEach } from './tool.js';
export default class Component extends HTMLElement {
    constructor(template, props = {}, state = {}) {
        super();
        const svg = document.querySelector('svg') || ce('div');
        svg.setAttribute('width', 0);
        svg.setAttribute('height', 0);
        if (!template) {
            console.error("Template can't be null!");
            return;
        }
        const shadow = this.attachShadow({
            mode: 'open'
        });
        let tmp;
        tmp = ce('template');
        tmp.innerHTML = svg.cloneNode(true).outerHTML + template;
        const node = tmp.content.cloneNode(true);
        shadow.appendChild(node);
        this._initAttr();
        this._initProps(props);
        this._initState(state);
        this._xEventBinding();
    }
    appendChild(dom) {
        this.shadowRoot.appendChild(dom);
    }
    qs(selector) {
        return this.shadowRoot.querySelector(selector);
    }
    qsa(selector) {
        return this.shadowRoot.querySelectorAll(selector) || [];
    }
    on(eventName, selector, cb) {
        on(eventName, this.shadowRoot.querySelector(selector), cb);
    }
    onPropsChange(newProps, oldProps) {
    }
    watch(property, cb) {
        this.dispatcher[property] = this.dispatcher[property] || [];
        this.dispatcher[property].push(cb);
    }
    getComponentProps(name) {
        return components[name];
    }
    getComponentsName() {
        return Object.keys(components);
    }
    _initAttr() {
        this.attr = {};
        this.getAttributeNames().forEach(name => {
            this.attr[name] = this.getAttribute(name);
        });
    }
    connectedCallback() {
        if (this.parent) {
            for (const p in this._props) {
                if (this._props[p] !== '&') {
                    const parentProperty = this.attr[p];
                    const value = this.parent.state[parentProperty];
                    if (this._props[p] === '=') {
                        this.state[p] = value;
                    } else if ('function' === typeof this._props[p]) {
                        this._props[p](value);
                    }
                    const oldProps = { ...this.props };
                    this.onPropsChange(this.props, oldProps);
                }
            }
        }
    }
    _initProps(props = {}) {
        this._props = props;
        this.props = {};
        const prefix = '_p_';
        for (const p in props) {
            if (props[p] !== '&') {
                Object.defineProperty(this.props, p, {
                    get: () => {
                        return this[prefix + p];
                    },
                    set: value => {
                        if (props[p] === '=') {
                            this.state[p] = value;
                        } else if ('function' === typeof props[p]) {
                            props[p](value);
                        }
                        const oldProps = {
                            ...this.props
                        };
                        this[prefix + p] = value;
                        this.onPropsChange(this.props, oldProps);
                    }
                });
            }
        }
    }
    _initState(state) {
        this.state = {};
        this.dispatcher = {};
        const dataBinding = dom => {
            if (['INPUT', 'TEXTAREA'].indexOf(dom.tagName) > -1) {
                dom.addEventListener('input', e => {
                    this.state[attr(dom, 'x-model')] = e.target.value;
                });
            } else if (['SELECT'].indexOf(dom.tagName) > -1) {
                dom.addEventListener('change', e => {
                    this.state[attr(dom, 'x-model')] = e.target.value;
                });
            }
        };
        const defineProperty = p => {
            const prefix = '_s_';
            Object.defineProperty(this.state, p, {
                get: () => {
                    return this[prefix + p];
                },
                set: value => {
                    if (this[prefix + p] !== value) {
                        this.dispatcher[p].forEach(fun => fun(value, this[prefix + p]));
                        this[prefix + p] = value;
                    }
                }
            });
        };
        const map = {
            'x-hide'(value) {
                this.style.display = value ? 'none' : '';
            },
            'x-show'(value) {
                this.style.display = value ? '' : 'none';
            },
            'x-bind'(value) {
                this.textContent = undefined === value ? '' : value;
            },
            'x-model'(value) {
                this.value = undefined === value ? '' : value;
            },
            'x-class'(value) {
                this.initClass = this.initClass || [...this.classList] || [];
                let classList = [];
                if (typeof value === 'string') {
                    classList = value.split(/\s+/);
                } else if (Object.prototype.toString.call(value) === '[object Array]') {
                    classList = value;
                }
                this.setAttribute('class', [...this.initClass, ...classList].join(' '));
            },
            'x-src'(value) {
                this.setAttribute('src', value);
            },
            'x-style'(value = {}) {
                for (const p in value) {
                    this.style[p] = value[p];
                }
            },
            'x-title'(value = '') {
                this.title = value;
            },
            'x-readonly'(value = false) {
                if (value) {
                    this.setAttribute('readonly', '');
                } else {
                    this.removeAttribute('readonly');
                }
            },
            'x-href'(value = '') {
                let use = this.querySelector('use');
                if (!use) {
                    use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
                    this.appendChild(use);
                }
                use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', value);
            },
        };
        const componentSelector = this.getComponentsName();
        componentSelector.forEach(sel => {
            forEach(this.qsa(sel), cmp => {
                const props = this.getComponentProps(sel);
                cmp.parent = this;
                for (const p in props) {
                    const prop = p.replace(/[A-Z]/g, s => '-' + s.toLowerCase());
                    const property = attr(cmp, prop);
                    if (props[p] === '&') {
                        if (cmp.props && this[property]) {
                            cmp.props[p] = this[property].bind(this);
                        }
                    } else {
                        this.dispatcher[property] = this.dispatcher[property] || [];
                        this.dispatcher[property].push(value => {
                            if (cmp.props) {
                                cmp.props[p] = value;
                            }
                        });
                    }
                }
            });
        });
        for (const p in map) {
            forEach(this.qsa(`[${p}]`), dom => {
                const property = attr(dom, p).split('.').shift();
                this.dispatcher[property] = this.dispatcher[property] || [];
                const fn = map[p].bind(dom);
                fn(this.state[property]);
                this.dispatcher[property].push(fn);
                if (p === 'x-model') {
                    dataBinding(dom);
                }
            });
        }
        for (const property in this.dispatcher) {
            defineProperty(property);
        }
        for (const p in state) {
            if (typeof state[p] === 'function') {
                Object.getOwnPropertyNames(this.state).indexOf(p) < 0 && defineProperty(p);
                this.dispatcher[p] = this.dispatcher[p] || [];
                this.dispatcher[p].push(state[p]);
            } else {
                this.state[p] = state[p];
            }
        }
    }
    _xEventBinding() {
        const map = ['x-click', 'x-keyup', 'x-change', 'x-blur'];
        map.forEach(event => {
            forEach(this.qsa(`[${event}]`), dom => {
                const property = attr(dom, event);
                const fnName = property.split('(')[0];
                const params = property.indexOf('(') > 0 ? property.replace(/.*\((.*)\)/, '$1').split(',') : [];
                let args = [];
                params.forEach(param => {
                    const p = param.trim();
                    const str = p.replace(/^'(.*)'$/, '$1').replace(/^"(.*)"$/, '$1');
                    if (str !== p) { 
                        args.push(str);
                    } else if (p === 'true' || p === 'false') { 
                        args.push(p === 'true');
                    } else if (!isNaN(p)) {
                        args.push(p * 1);
                    } else {
                        args.push(this.state[p]);
                    }
                });
                on(event.replace('x-', ''), dom, e => {
                    this[fnName](...args, e);
                });
            });
        });
    }
}
const components = {};
export const defineComponent = (name, componentClass) => {
    customElements.define(name, componentClass);
    const cmp = new componentClass();
    components[name] = cmp._props || {};
};